package com.giggle.nacos.registry.dubbo;

import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.NamingService;
import com.alibaba.nacos.api.naming.listener.EventListener;
import com.alibaba.nacos.api.naming.listener.NamingEvent;
import com.alibaba.nacos.api.naming.pojo.Instance;
import com.alibaba.nacos.api.naming.pojo.ListView;
import com.giggle.nacos.helper.NacosHelper;
import org.apache.dubbo.common.URL;
import org.apache.dubbo.common.URLBuilder;
import org.apache.dubbo.common.logger.Logger;
import org.apache.dubbo.common.logger.LoggerFactory;
import org.apache.dubbo.common.utils.StringUtils;
import org.apache.dubbo.common.utils.UrlUtils;
import org.apache.dubbo.registry.NotifyListener;
import org.apache.dubbo.registry.support.FailbackRegistry;

import java.util.*;
import java.util.concurrent.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @author guozichen
 * @ClassName:
 * @Description: (这里用一句话描述累的作用)
 * @date 2020/8/26 17:54
 */
public class NacosRegistry extends FailbackRegistry {
    private static final List<String> ALL_SUPPORTED_CATEGORIES = Arrays.asList("providers", "consumers", "routers", "configurators");
    private static final int CATEGORY_INDEX = 0;
    private static final int SERVICE_INTERFACE_INDEX = 1;
    private static final int SERVICE_VERSION_INDEX = 2;
    private static final int SERVICE_GROUP_INDEX = 3;
    private static final String WILDCARD = "*";
    private static final String SERVICE_NAME_SEPARATOR = System.getProperty("dubbo.dubbo.name.separator", ":");
    private static final int PAGINATION_SIZE = Integer.getInteger("dubbo.dubbo.names.pagination.size", 100);
    private static final long LOOKUP_INTERVAL = Long.getLong("dubbo.dubbo.names.lookup.interval", 30L);
    private static final String DUBBO_METADATA_SERVICE_NAME = "com.alibaba.cloud.dubbo.service.DubboMetadataService";
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    private final NamingService namingService;
    private final ConcurrentMap<String, EventListener> nacosListeners;
    private volatile ScheduledExecutorService scheduledExecutorService;

    public NacosRegistry(URL url, NamingService namingService) {
        super(url);
        this.namingService = namingService;
        this.nacosListeners = new ConcurrentHashMap();
    }

    @Override
    public boolean isAvailable() {
        return "UP".equals(this.namingService.getServerStatus());
    }

    @Override
    public List<URL> lookup(URL url) {
        List<URL> urls = new LinkedList();
        this.execute((namingService) -> {
            Set<String> serviceNames = this.getServiceNames(url, (NotifyListener)null);
            String groupName = NacosHelper.getNacosGroupName(url);
            Iterator var6 = serviceNames.iterator();

            while(var6.hasNext()) {
                String serviceName = (String)var6.next();
                List<Instance> instances = namingService.getAllInstances(serviceName, groupName);
                urls.addAll(this.buildURLs(url, instances));
            }

        });
        return urls;
    }

    @Override
    public void doRegister(URL url) {
        String serviceName = this.getServiceName(url);
        Boolean registerEnabled = true;
        if (StringUtils.isNotEmpty(serviceName) && serviceName.indexOf("com.alibaba.cloud.dubbo.service.DubboMetadataService") > 0) {
            registerEnabled = NacosHelper.getMetadataRegisterEnabled();
        }

        if (registerEnabled) {
            Instance instance = this.createInstance(url);
            this.execute((namingService) -> {
                String groupName = NacosHelper.getProviderGroupName(url);
                namingService.registerInstance(serviceName, groupName, instance);
            });
        }

    }

    @Override
    public void doUnregister(URL url) {
        String groupName = NacosHelper.getProviderGroupName(url);
        this.execute((namingService) -> {
            String serviceName = this.getServiceName(url);
            Instance instance = this.createInstance(url);
            namingService.deregisterInstance(serviceName, groupName, instance.getIp(), instance.getPort());
        });
    }

    @Override
    public void doSubscribe(URL url, NotifyListener listener) {
        Set<String> serviceNames = this.getServiceNames(url, listener);
        this.doSubscribe(url, listener, serviceNames);
    }

    private void doSubscribe(URL url, NotifyListener listener, Set<String> serviceNames) {
        String groupName = NacosHelper.getNacosGroupName(url);
        this.execute((namingService) -> {
            Iterator var6 = serviceNames.iterator();

            while(var6.hasNext()) {
                String serviceName = (String)var6.next();
                List<Instance> instances = namingService.getAllInstances(serviceName, groupName);
                this.notifySubscriber(url, listener, instances);
                this.subscribeEventListener(serviceName, url, listener);
            }

        });
    }

    @Override
    public void doUnsubscribe(URL url, NotifyListener listener) {
        if (this.isAdminProtocol(url)) {
            this.shutdownServiceNamesLookup();
        }

    }

    private void shutdownServiceNamesLookup() {
        if (this.scheduledExecutorService != null) {
            this.scheduledExecutorService.shutdown();
        }

    }

    private Set<String> getServiceNames(URL url, NotifyListener listener) {
        if (this.isAdminProtocol(url)) {
            this.scheduleServiceNamesLookup(url, listener);
            return this.getServiceNamesForOps(url);
        } else {
            return this.getServiceNames0(url);
        }
    }

    private Set<String> getServiceNames0(URL url) {
        NacosServiceName serviceName = this.createServiceName(url);
        String groupName = NacosHelper.getNacosGroupName(url);
        Set serviceNames;
        if (serviceName.isConcrete()) {
            serviceNames = Collections.singleton(serviceName.toString());
        } else {
            serviceNames = this.filterServiceNames(serviceName, groupName);
        }

        return serviceNames;
    }

    private Set<String> filterServiceNames(NacosServiceName serviceName, String groupName) {
        Set<String> serviceNames = new LinkedHashSet();
        this.execute((namingService) -> {
            Stream<NacosServiceName> stream = namingService.getServicesOfServer(1, 2147483647, groupName).getData().stream().map(NacosServiceName::new);
            serviceName.getClass();
            serviceNames.addAll((Collection)stream.filter(item -> serviceName.isCompatible(item)).map(NacosServiceName::toString).collect(Collectors.toList()));
        });
        return serviceNames;
    }

    private boolean isAdminProtocol(URL url) {
        return "admin".equals(url.getProtocol());
    }

    private void scheduleServiceNamesLookup(URL url, NotifyListener listener) {
        String groupName = NacosHelper.getNacosGroupName(url);
        if (this.scheduledExecutorService == null) {
            this.scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
            this.scheduledExecutorService.scheduleAtFixedRate(() -> {
                Set<String> serviceNames = this.getAllServiceNames(groupName);
                this.filterData(serviceNames, (serviceName) -> {
                    boolean accepted = false;
                    Iterator var2 = ALL_SUPPORTED_CATEGORIES.iterator();

                    while(var2.hasNext()) {
                        String category = (String)var2.next();
                        String prefix = category + SERVICE_NAME_SEPARATOR;
                        if (serviceName != null && serviceName.startsWith(prefix)) {
                            accepted = true;
                            break;
                        }
                    }

                    return accepted;
                });
                this.doSubscribe(url, listener, serviceNames);
            }, LOOKUP_INTERVAL, LOOKUP_INTERVAL, TimeUnit.SECONDS);
        }

    }

    private Set<String> getServiceNamesForOps(URL url) {
        String groupName = NacosHelper.getNacosGroupName(url);
        Set<String> serviceNames = this.getAllServiceNames(groupName);
        this.filterServiceNames(serviceNames, url);
        return serviceNames;
    }

    private Set<String> getAllServiceNames(String groupName) {
        Set<String> serviceNames = new LinkedHashSet();
        this.execute((namingService) -> {
            int pageIndex = 1;
            ListView<String> listView = namingService.getServicesOfServer(pageIndex, PAGINATION_SIZE, groupName);
            List<String> firstPageData = listView.getData();
            serviceNames.addAll(firstPageData);
            int count = listView.getCount();
            int pageNumbers = count / PAGINATION_SIZE;
            int remainder = count % PAGINATION_SIZE;
            if (remainder > 0) {
                ++pageNumbers;
            }

            while(pageIndex < pageNumbers) {
                ++pageIndex;
                listView = namingService.getServicesOfServer(pageIndex, PAGINATION_SIZE, groupName);
                serviceNames.addAll(listView.getData());
            }

        });
        return serviceNames;
    }

    private void filterServiceNames(Set<String> serviceNames, URL url) {
        List<String> categories = this.getCategories(url);
        String targetServiceInterface = url.getServiceInterface();
        String targetVersion = url.getParameter("version", "");
        String targetGroup = url.getParameter("group", "");
        this.filterData(serviceNames, (serviceName) -> {
            String[] segments = serviceName.split(SERVICE_NAME_SEPARATOR, -1);
            int length = segments.length;
            if (length != 4) {
                return false;
            } else {
                String category = segments[0];
                if (!categories.contains(category)) {
                    return false;
                } else {
                    String serviceInterface = segments[1];
                    if (!"*".equals(targetServiceInterface) && !StringUtils.isEquals(targetServiceInterface, serviceInterface)) {
                        return false;
                    } else {
                        String version = segments[2];
                        if (!"*".equals(targetVersion) && !StringUtils.isEquals(targetVersion, version)) {
                            return false;
                        } else {
                            String group = segments[3];
                            return group == null || "*".equals(targetGroup) || StringUtils.isEquals(targetGroup, group);
                        }
                    }
                }
            }
        });
    }

    private <T> void filterData(Collection<T> collection, NacosRegistry.NacosDataFilter<T> filter) {
        collection.removeIf((data) -> {
            return !filter.accept(data);
        });
    }

    /** @deprecated */
    @Deprecated
    private List<String> doGetServiceNames(URL url) {
        List<String> categories = this.getCategories(url);
        List<String> serviceNames = new ArrayList(categories.size());
        Iterator var4 = categories.iterator();

        while(var4.hasNext()) {
            String category = (String)var4.next();
            String serviceName = this.getServiceName(url, category);
            serviceNames.add(serviceName);
        }

        return serviceNames;
    }

    private List<URL> toUrlWithEmpty(URL consumerURL, Collection<Instance> instances) {
        List<URL> urls = this.buildURLs(consumerURL, instances);
        if (urls.size() == 0) {
            URL empty = URLBuilder.from(consumerURL).setProtocol("empty").addParameter("category", "providers").build();
            urls.add(empty);
        }

        return urls;
    }

    private List<URL> buildURLs(URL consumerURL, Collection<Instance> instances) {
        List<URL> urls = new LinkedList();
        if (instances != null && !instances.isEmpty()) {
            Iterator var4 = instances.iterator();

            while(var4.hasNext()) {
                Instance instance = (Instance)var4.next();
                URL url = this.buildURL(instance);
                if (UrlUtils.isMatch(consumerURL, url)) {
                    urls.add(url);
                }
            }
        }

        return urls;
    }

    private void subscribeEventListener(String serviceName, URL url, NotifyListener listener) throws NacosException {
        if (!this.nacosListeners.containsKey(serviceName)) {
            EventListener eventListener = (event) -> {
                if (event instanceof NamingEvent) {
                    NamingEvent e = (NamingEvent)event;
                    this.notifySubscriber(url, listener, e.getInstances());
                }

            };
            String groupName = NacosHelper.getNacosGroupName(url);
            this.namingService.subscribe(serviceName, groupName, eventListener);
            this.nacosListeners.put(serviceName, eventListener);
        }

    }

    private void notifySubscriber(URL url, NotifyListener listener, Collection<Instance> instances) {
        List<Instance> healthyInstances = new LinkedList(instances);
        if (healthyInstances.size() > 0) {
            this.filterHealthyInstances(healthyInstances);
        }

        List<URL> urls = this.toUrlWithEmpty(url, healthyInstances);
        this.notify(url, listener, urls);
    }

    private List<String> getCategories(URL url) {
        return "*".equals(url.getServiceInterface()) ? ALL_SUPPORTED_CATEGORIES : Arrays.asList("providers");
    }

    private URL buildURL(Instance instance) {
        Map<String, String> metadata = instance.getMetadata();
        String protocol = (String)metadata.get("protocol");
        String path = (String)metadata.get("path");
        return new URL(protocol, instance.getIp(), instance.getPort(), path, instance.getMetadata());
    }

    private Instance createInstance(URL url) {
        String category = url.getParameter("category", "providers");
        URL newURL = url.addParameter("category", category);
        newURL = newURL.addParameter("protocol", url.getProtocol());
        newURL = newURL.addParameter("path", url.getPath());
        String ip = url.getHost();
        int port = url.getPort();
        Instance instance = new Instance();
        instance.setIp(ip);
        instance.setPort(port);
        instance.setMetadata(new HashMap(newURL.getParameters()));
        return instance;
    }

    private NacosServiceName createServiceName(URL url) {
        return NacosServiceName.valueOf(url);
    }

    private String getServiceName(URL url) {
        return this.getServiceName(url, url.getParameter("category", "providers"));
    }

    private String getServiceName(URL url, String category) {
        return category + SERVICE_NAME_SEPARATOR + url.getColonSeparatedKey();
    }

    private void execute(NacosRegistry.NamingServiceCallback callback) {
        try {
            callback.callback(this.namingService);
        } catch (NacosException var3) {
            if (this.logger.isErrorEnabled()) {
                this.logger.error(var3.getErrMsg(), var3);
            }
        }

    }

    private void filterHealthyInstances(Collection<Instance> instances) {
        this.filterData(instances, Instance::isEnabled);
    }

    interface NamingServiceCallback {
        void callback(NamingService var1) throws NacosException;
    }

    private interface NacosDataFilter<T> {
        boolean accept(T var1);
    }
}

